--[[
    This module contains a lot of basic functions that modules will want to do.
--]]

local CoreLib = {};

--[[
    PRINTING FUNCTIONS
--]]

-- returns a string of all the args passed to it
local function stringify(...)
    local num = select("#", ...);
    
    if (num == 1) then
        return tostring(...);
    else
        return tostring((select(1, ...))), stringify(select(2, ...));
    end 
end

-- prints the args passed to it.  nothing special
function CoreLib:Print(...)
    if (DEFAULT_CHAT_FRAME) then
        DEFAULT_CHAT_FRAME:AddMessage(strconcat(stringify(...))); 
    end
end

-- prints a colored message!
function CoreLib:ColorPrint(r, g, b, ...)
    if (DEFAULT_CHAT_FRAME) then
        DEFAULT_CHAT_FRAME:AddMessage(strconcat(stringify(...)), r, g, b);
    end
end

-- prints a message to a specific frame
function CoreLib:FramePrint(frame, ...)
    if (frame) then
        frame:AddMessage(strconcat(stringify(...)));
    end
end

-- prints a message, but begins it with a header of the module's name
function CoreLib:HeaderPrint(...)
    self:Print("|cff00ffff", self:GetName(), "|r: ", ...);
end

-- prints out a debugging message, if debugging messages are enabled
function CoreLib:Debug(...)
    local debugchannel = self:Get("debug")
    
    if (debugchannel and not (debugchannel == "" or debugchannel == "off")) then
        local frame = getglobal("ChatFrame"..debugchannel);
        
        if ((not (debugchannel == "on")) and frame) then 
            self:FramePrint(frame, ...);
        else 
            self:Print(...);
        end
    end
end

--[[
    SETTINGS FUNCTIONS
--]]
-- adds in default settings into the settings
function CoreLib:AddDefaultSettings(newSettings)
    local currSettings = self.libraries["CoreLib"].defaultSettings;
    for i, v in pairs(newSettings) do
        currSettings[i] = v;
    end
end

-- clears out the settings, leaving the default ones
function CoreLib:ClearSettings()
    local settings = AsheylaLib_Settings[ASHEYLALIB_ACTIVE_PROFILE][self:GetName()];
    if settings then
        self:ClearTable(settings);
        self:UpdateSettings();
    end
end

function CoreLib:Get(...)
    if not AsheylaLib.loaded then return; end
    local setting = self:GetUserSet(...);
    if setting == nil then
        return self:GetDefault(...);
    else
        return setting;
    end
end

function CoreLib:GetUserSet(...)
    if not AsheylaLib.loaded then return; end
    local setting = AsheylaLib_Settings[ASHEYLALIB_ACTIVE_PROFILE][self:GetName()];
    if setting then
        for i = 1, select("#", ...) do
            setting = setting[select(i, ...)];
            if (setting == nil) then
                break;
            end
        end
    end
    
    return setting;
end

function CoreLib:GetDefault(...)
    if not AsheylaLib.loaded then return; end
    local setting = self.libraries["CoreLib"].defaultSettings;
    for i = 1, select("#", ...) do
        setting = setting[select(i, ...)];
        if (setting == nil) then
            break;
        end
    end
    
    return setting;
end

function CoreLib:Set(...)
    if not AsheylaLib.loaded then return; end
    local setting = AsheylaLib_Settings[ASHEYLALIB_ACTIVE_PROFILE][self:GetName()];
    if (not setting) then
        setting = {};
        AsheylaLib_Settings[ASHEYLALIB_ACTIVE_PROFILE][self:GetName()] = setting;
    end
    
    local num = select("#", ...);
    if type(select(1, ...)) == "table" and num == 3 then
        setting = select(1, ...);
    else
        for i = 1, num - 2 do
            local val = select(i, ...);
            setting[val] = setting[val] or {};
            setting = setting[val];
        end
    end
    
    local ind = select(num - 1, ...);
    local val = select(num, ...);
    
    setting[ind] = val;
    self:UpdateSettings();
end

-- calls any functions that have registered to settings changes
function CoreLib:UpdateSettings()
    if (self.libraries["CoreLib"].updateScripts) then
        for index,value in ipairs(self.libraries["CoreLib"].updateScripts) do 
            value(self);
        end
    end
end

-- adds a function to listen to settings changes
function CoreLib:AddSettingsUpdateScript(func)
    self.libraries["CoreLib"].updateScripts = self.libraries["CoreLib"].updateScripts or {};
    table.insert(self.libraries["CoreLib"].updateScripts, func);
end

--[[
    MISC FUNCTIONS
--]]

-- returns the module's name
function CoreLib:GetName()
    return self.libraries["CoreLib"].name;
end

-- sets a script!
function CoreLib:SetScript(script, func)
    self.libraries["CoreLib"].scripts = self.libraries["CoreLib"].scripts or {};
    self.libraries["CoreLib"].scripts[script] = func;
end

-- gets a script!
function CoreLib:GetScript(script)
    return self.libraries["CoreLib"].scripts and self.libraries["CoreLib"].scripts[script];
end

-- calls a script!
function CoreLib:CallScript(script, ...)
    local func = self:GetScript(script);
    if (func) then
        func(self, ...)
    end
end

-- registers an event!
function CoreLib:RegisterEvent(event, func)
    local lib = AsheylaLib;
    lib.events[event] = lib.events[event] or {};
    lib.events[event][self] = func or 1;
end

-- unregisters an event!
function CoreLib:UnregisterEvent(event)
    local lib = AsheylaLib;
    lib.events[event] = lib.events[event] or {};
    lib.events[event][self] = nil;
end

-- checks if an event is registered!
function CoreLib:IsRegisteredEvent(event)
    return AsheylaLib.events[event] and AsheylaLib.events[event][self];
end

-- makes a /command!
function CoreLib:MakeSlashCmd(...)
    local global = string.upper(self:GetName());
    
    SlashCmdList[global] = function(msg) 
        if (self.Commands) then 
            self:Commands(msg);  
        end 
    end;
    
    for i = 1, select("#", ...) do
        local slash = select(i, ...);
        setglobal("SLASH_" .. global .. i, slash);
    end
end

-- prints out information about me!
function CoreLib:AddAuthorInfo()
    self:Print("|cff00ffffDoTimer Version/Author Info:|r");
    self:Print("|cff00ff00Current version|r: ",AsheylaLib:ReturnVersion());
    self:Print("|cff00ff00Date Uploaded|r: ",AsheylaLib:ReturnDateUploaded());
    self:Print("|cff00ff00Author|r: Asheyla <Vindication>, Shattered Hand (Horde)");
    self:Print("|cff00ff00Author's Alt|r: Xanido <Hearts>, Anetheron (Alliance)");
    self:Print("|cff00ff00Email|r: ross456@gmail.com");
    self:Print("|cff00ff00AIM|r: IBerian3209");
    self:Print("|cff00ff00Paypal Donations|r: ross456@gmail.com");
end

--[[
    TABLE FUNCTIONS
--]]

-- returns a copy of the table passed to it
function CoreLib:CopyTable(oldtable, suppress, tabledata)
    tabledata = tabledata or {};
    local newtable = {};
    
    if (not tabledata[oldtable]) then
        tabledata[oldtable] = newtable;
    end
    
    for index,value in pairs(oldtable) do
        if (type(index) == "table") then
            if (tabledata[index]) then
                index = tabledata[index]
            else
                index = CoreLib:CopyTable(index, suppress, tabledata);
            end
        end
        
        if (type(value) == "table") then
            if (tabledata[value]) then
                newtable[index] = tabledata[value]
            else
                newtable[index] = CoreLib:CopyTable(value, suppress, tabledata);
            end
        else
            newtable[index] = value;
        end
    end
    
    if (not suppress) then
        local meta = getmetatable(oldtable);
        if (meta) then 
            setmetatable(newtable, meta);
        end
    end
    
    return newtable;
end

-- combines two tables, giving precedence to table1, without worry of infinite recursion (be careful!)
function CoreLib:CombineTables(table1, table2)
    for index,value in pairs(table2) do
        if (table1[index]) then
            if (type(table1[index]) == "table" and type(value) == "table") then 
                table1[index] = self:CombineTables(table1[index], value);
            end
        else
            table1[index] = value;
        end
    end
    
    return table1;
end

-- this next bit of code deals with creating temporary tables.  it recycles them!

local tablepool = {}
local currenttables = {}
local setupdate

-- acquire a table!  'auto' means that you only need it for 1 frame
function CoreLib:AcquireTable(auto)
    local t = table.remove(tablepool,1) or {}
    if auto then
        table.insert(currenttables,t)
        setupdate(true)
    end
    self:ClearTable(t)
    return t
end

-- release multiple tables at a time
function CoreLib:ReleaseTable(...)
    for i = 1,select("#",...) do
        local t = select(i, ...);
        if (type(t) == "table") then
            self:ClearTable(t);
            setmetatable(t, nil);
            table.insert(tablepool, t);
        end
    end
end

-- nils out a table
function CoreLib:ClearTable(t)
    for index,value in pairs(t) do 
        t[index] = nil;
    end
end

local f = CreateFrame("Frame");

local updatefunc = function()
    if (#currenttables > 0) then
        CoreLib:ReleaseTable(unpack(currenttables));
        for i = #currenttables,1,-1 do 
            table.remove(currenttables, i);
        end
        setupdate(false);
    end
end

function setupdate(scanning)
    if (scanning) then
        f:SetScript("OnUpdate", updatefunc);
    else
        f:SetScript("OnUpdate", nil);
    end
end

AsheylaLib:CreateLibrary("CoreLib", CoreLib)
